package com.ep.thirdPlatformProject.workWx.handler;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.ep.thirdPlatformProject.system.util.MinioUtil;
import com.ep.thirdPlatformProject.workWx.JsonUtils;
import com.ep.thirdPlatformProject.workWx.builder.TextBuilder;
import com.ep.thirdPlatformProject.workWx.mongo.ClientInfo;
import com.ep.thirdPlatformProject.workWx.mongo.ExportConcatEntity;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.cp.api.WxCpKfService;
import me.chanjar.weixin.cp.api.WxCpMediaService;
import me.chanjar.weixin.cp.api.WxCpService;
import me.chanjar.weixin.cp.api.WxCpUserService;
import me.chanjar.weixin.cp.bean.WxCpUser;
import me.chanjar.weixin.cp.bean.external.contact.ExternalContact;
import me.chanjar.weixin.cp.bean.kf.*;
import me.chanjar.weixin.cp.bean.kf.msg.WxCpKfResourceMsg;
import me.chanjar.weixin.cp.bean.kf.msg.WxCpKfTextMsg;
import me.chanjar.weixin.cp.bean.message.WxCpXmlMessage;
import me.chanjar.weixin.cp.bean.message.WxCpXmlOutMessage;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.io.File;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 微信客服接口
 * <p>
 * 微信客服由腾讯微信团队为企业打造，用于满足企业的客服需求，帮助企业做好客户服务。企业可以在微信内、外各个场景中接入微信客服，
 * 用户可以发起咨询，企业可以进行回复。
 * 企业可在微信客服官网使用企业微信扫码开通微信客服，开通后即可使用。
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class MsgHandler extends AbstractHandler {

    private final RedisTemplate<String, String> redisTemplate;

    private final RedisTemplate<String, Object> userTemplate;

    private final MinioUtil minioUtil;

    private final MongoTemplate mongoTemplate;


    private final static String WORK_WX_EMP_LIST = "WORK_WX_EMP_LIST";

    @Override
    public WxCpXmlOutMessage handle(WxCpXmlMessage wxMessage, Map<String, Object> context, WxCpService cpService,
                                    WxSessionManager sessionManager) {
        try {
            final String msgType = wxMessage.getMsgType();
            // 如果msgType没有，就自己根据具体报文内容做处理
            String next = "";
            boolean hasNext = false;
            assert msgType != null;
            WxCpKfMsgListResp res = null;

            if (msgType.equals(WxConsts.XmlMsgType.EVENT)) {
                String token = wxMessage.getToken();
                String openKfId = wxMessage.getOpenKfId();
                // 客服消息
                WxCpKfService kfService = cpService.getKfService();

                if (Boolean.TRUE.equals(redisTemplate.hasKey(openKfId))) {
                    next = redisTemplate.boundValueOps(openKfId).get();
                }
                // 获取到最新的消息记录
                while (!hasNext) {
                    res = kfService.syncMsg(next, token, 1000, 0, openKfId);
                    hasNext = res.getHasMore() == 0;
                }
                // 绑定上次光标值
                next = res.getNextCursor();
                redisTemplate.opsForValue().set(openKfId, next);
                if (res.getMsgList().isEmpty()) {
                    log.info("上次聊天消息记录为空");
                    return null;
                }
                WxCpKfMsgListResp.WxCpKfMsgItem latestNotice = res.getMsgList().get(res.getMsgList().size() - 1);

                // 查看是否接入会话
                if (latestNotice.getEvent() != null && "session_status_change".equals(latestNotice.getEvent().getEventType())) {
                    if (latestNotice.getEvent().getChangeType() == null) {
                        log.info("挂起来了");
                        return null;
                    }
                    switch (latestNotice.getEvent().getChangeType()) {
                        case 1 -> log.info("从接待池接入会话");
                        case 2 -> log.info("转接会话");
                        case 3 -> log.info("结束会话");
                        case 4 -> log.info("重新接入已结束/已转接会话");
                    }
                    return null;
                }
                if (latestNotice.getEvent() != null && "servicer_status_change".equals(latestNotice.getEvent().getEventType())) {
                    switch (latestNotice.getEvent().getChangeType()) {
                        case 2 -> log.info("客服消息消息暂时挂起");
                        case 3 -> log.info("客服消息消息退出");
                    }
                    return null;
                }
                if (latestNotice.getEvent() != null && "enter_session".equals(latestNotice.getEvent().getEventType())) {
                    String customerName = this.getCustomerName(kfService, latestNotice.getEvent().getExternalUserId());

                    log.error("{}:客户进入客服咨询！", customerName);
                    return null;
                }

                List<WxCpKfServicerListResp.WxCpKfServicerStatus> servicerList = kfService.listServicer(openKfId).getServicerList();
                // 获取到接待中人员的信息
                WxCpKfServicerListResp.WxCpKfServicerStatus kfInfo = servicerList.stream().filter(item -> item.getStatus() == 0).findFirst().orElse(servicerList.get(0));
                // 外部联系人id
                String externalUserId = latestNotice.getExternalUserId();

                // 外部联系人name
                String customerName = this.getCustomerName(kfService, externalUserId);
                // 对话实体消息
                ExportConcatEntity entity;
                // 当发送者id为空 客户id不为空的情况下，此时为用户发送消息
                if (latestNotice.getServicerUserId() == null && externalUserId != null) {
                    // 记录对应的消息到数据库中去 key 为客服id+客户id
                    entity = this.buildBaseConcatEntity(customerName, null, kfInfo.getUserId());

                    // 判断当前时间是否是工作时间
                    if (this.inWorkHours(Objects.requireNonNull(redisTemplate.boundValueOps("isWork").get()))) {
                        // 转给对应的人
                        kfService.transServiceState(openKfId, externalUserId, 3, kfInfo.getUserId());
                    } else {
                        // 提示客户为非工作日时间
                        // 状态变为api自动回复消息
                        WxCpKfServiceStateResp state = kfService.getServiceState(openKfId, externalUserId);
                        log.info("当前会话状态：{}", state.getServiceState());

                        String msgCode = "";               // 记录是否可以会话转变的ke

                        WxCpKfServiceStateTransResp stateResp;

                        if (state.getServiceState() == 1) {
                            // 会话变为结束
                            stateResp = kfService.transServiceState(openKfId, externalUserId, 4, "");
                            msgCode = stateResp.getMsgCode();
                            Assert.notNull(msgCode, () -> new RuntimeException("返回编码不能为空！"));

                        }
                        if (state.getServiceState() == 2) {
                            if (kfInfo.getStatus() == 1) {
                                log.info("当前没有员工接待！状态转化需要有员工接待！");
                                return null;
                            } else {
                                kfService.transServiceState(openKfId, externalUserId, 3, kfInfo.getUserId());
                                // 会话变为结束
                                stateResp = kfService.transServiceState(openKfId, externalUserId, 4, "");
                                msgCode = stateResp.getMsgCode();
                                Assert.notNull(msgCode, () -> new RuntimeException("返回编码不能为空！"));
                            }

                        }
                        if (state.getServiceState() == 0) {
                            kfService.transServiceState(openKfId, externalUserId, 1, "");
                            // 会话变为结束
                            stateResp = kfService.transServiceState(openKfId, externalUserId, 4, "");
                            msgCode = stateResp.getMsgCode();
                            Assert.notNull(msgCode, () -> new RuntimeException("返回编码不能为空！"));
                        }

                        WxCpKfMsgSendRequest request = new WxCpKfMsgSendRequest();
                        WxCpKfTextMsg textMsg = new WxCpKfTextMsg();
                        request.setMsgType("text");
                        textMsg.setContent("当前为非工作日，请您在工作日再咨询,多谢谅解！");
                        request.setText(textMsg);
                        request.setCode(msgCode);

                        kfService.sendMsgOnEvent(request);
                    }

                } else {
                    // 此时为客服发送的消息
                    String receptionist = this.getReceptionistNameByUserId(cpService.getUserService(), kfInfo.getUserId());
                    // 记录对应的消息到数据库中去 key 为客服id+客户id
                    entity = this.buildBaseConcatEntity(null, receptionist, kfInfo.getUserId());
                }
                // 对话内容

                // 处理文本内容 填充到细节对话中去
                File file = this.dealMediaMessage(cpService, latestNotice, entity);
                // 获取客服名称
                String kfName = this.getKfName(openKfId, kfService);
                entity.setKfName(kfName);

                // 保存到mongodb中去
                this.saveToMongo(openKfId, externalUserId, customerName, entity);
            }
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        String content = "收到信息内容：" + JsonUtils.toJson(wxMessage);

        return new TextBuilder().build(content, wxMessage, cpService);

    }


    /**
     * 处理媒体信息
     *
     * @param cpService    cp服务
     * @param latestNotice 最新通知
     * @return {@link File }
     */
    private File dealMediaMessage(WxCpService cpService, WxCpKfMsgListResp.WxCpKfMsgItem latestNotice, ExportConcatEntity entity) {
        File download = null;
        try {
            String contentType = "";
            // 媒体消息
            WxCpMediaService mediaService = cpService.getMediaService();
            switch (latestNotice.getMsgType()) {
                case WxConsts.KefuMsgType.TEXT -> {
                    String content = latestNotice.getText().getContent();
                    log.info("收到的客户的明文消息是：{}", content);
                    entity.setContentType("文本");
                    entity.setContent(content);
                }
                case WxConsts.KefuMsgType.IMAGE -> {
                    WxCpKfResourceMsg file = latestNotice.getImage();
                    download = mediaService.download(file.getMediaId());
                    contentType = "图片";
                }

                case WxConsts.KefuMsgType.FILE -> {
                    WxCpKfResourceMsg file = latestNotice.getFile();
                    download = mediaService.download(file.getMediaId());
                    contentType = "文件";
                }

                case WxConsts.KefuMsgType.VOICE -> {
                    WxCpKfResourceMsg file = latestNotice.getVoice();
                    download = mediaService.download(file.getMediaId());
                    contentType = "语音";
                }
                case WxConsts.KefuMsgType.VIDEO -> {
                    WxCpKfResourceMsg video = latestNotice.getVideo();
                    download = mediaService.download(video.getMediaId());
                    contentType = "媒体";
                }
                default -> {
                    contentType ="消息类型未知！";
                }
            }
            String finalContentType = contentType;
            Opt.ofNullable(download).ifPresent(item -> {
                // 上传到minio中
                String fileName = IdUtil.getSnowflake().nextId() + "";

                String linkedFileName = minioUtil.upload(item, fileName);
                // 预览的文件url
                String previewUrl = minioUtil.getPreviewUrl(linkedFileName);
                // 下载的文件url
                String downloadUrl = previewUrl + "?response-content-type=application/octet-stream";
                log.info("minio预览的链接：{}，下载的链接为：{}", previewUrl, downloadUrl);
                entity.setContentType(finalContentType);
                entity.setContent(StrUtil.format("消息为{}消息，预览链接：{},下载链接：{}", finalContentType, previewUrl, downloadUrl));
            });
        } catch (Exception e) {
            log.error("获取媒体消息错误：{}", e.getMessage(), e);
        }
        return download;
    }


    /**
     * 获取kf名称
     *
     * @param openKfId  打开kf id
     * @param kfService kf服务
     * @return {@link String }
     * @throws WxErrorException Wx错误异常
     */
    private String getKfName(String openKfId, WxCpKfService kfService) throws WxErrorException {
        WxCpKfAccountListResp.AccountListDTO accountListDTO = kfService.listAccount(0, 100)
                .getAccountList().stream().filter(item -> item.getOpenKfid().equals(openKfId))
                .findFirst().orElseThrow(() -> new RuntimeException("请检查客服账号是否存在" + openKfId));
        return accountListDTO.getName();
    }

    /**
     * 获取客户名称
     *
     * @param kfService      kf服务
     * @param externalUserId 外部用户id
     * @return {@link String }
     */
    private String getCustomerName(WxCpKfService kfService, String externalUserId) {
        try {
            Assert.notNull(externalUserId, () -> new RuntimeException("客户咨询id不能为空！"));
            Query query = Query.query(Criteria.where("_id").is(externalUserId));
            ClientInfo client = mongoTemplate.findOne(query, ClientInfo.class);
            if (client == null) {
                WxCpKfCustomerBatchGetResp res = kfService.customerBatchGet(Collections.singletonList(externalUserId));
                if (res != null && !res.getCustomerList().isEmpty()) {
                    ExternalContact contact = res.getCustomerList().get(0);
                    String nickname = contact.getNickname();
                    log.info("当前用户的昵称为：{}", nickname);
                    ClientInfo clientInfo = new ClientInfo()
                            .setClientName(nickname)
                            .setId(externalUserId);
                    // 保存到mongo中去
                    mongoTemplate.insert(clientInfo);
                    return nickname;
                }


            }else {
                return client.getClientName();
            }
        } catch (Exception e) {
            log.error(" 获取客户错误:{}", e.getMessage());
            log.error("当前客户消息未知，只能暂且通过id记录：{}", externalUserId);
        }

        return externalUserId;
    }

    /**
     * 构建基本连接实体
     *
     * @param clientName 用户名
     */

    private ExportConcatEntity buildBaseConcatEntity(String clientName, String receptionist, String kfId) {

        return new ExportConcatEntity()
                .setClientName(clientName)
                .setReceptionistName(receptionist)
                .setReceptionistNo(kfId)
                .setCreateTime(DateUtil.now());
    }


    /**
     * 通过用户id获取接待员姓名
     *
     * @param userService 用户服务
     * @param userId      用户id
     * @return {@link String }
     * @throws WxErrorException Wx错误异常
     */
    private String getReceptionistNameByUserId(WxCpUserService userService, String userId) throws WxErrorException {

        BoundHashOperations<String, Object, Object> mapOp = userTemplate.boundHashOps(WORK_WX_EMP_LIST);

        if (ObjectUtil.isNull(mapOp.get(userId))) {
            WxCpUser user = userService.getById(userId);
            mapOp.put(user.getUserId(), user);
            mapOp.expire(20, TimeUnit.HOURS);
            return user.getName();
        }

        return ((WxCpUser) Objects.requireNonNull(mapOp.get(userId))).getName();

    }


    /**
     * 判断是否在工作时间
     *
     * @param kfId kf名字
     * @return {@link Boolean } true->工作时间，false->非工作时间
     */
    private Boolean inWorkHours(String kfId) {
        LocalDate now = LocalDate.now();
        Calendar calendar = Calendar.getInstance();
        int hours = calendar.get(Calendar.HOUR_OF_DAY);
        // 工作时间段
        boolean isWorkHour = hours >= 8 && hours <= 18;
        return switch (kfId) {
            case "d" -> !now.getDayOfWeek().equals(DayOfWeek.SUNDAY) && isWorkHour;
            case "e" -> false;
            default ->
                    !now.getDayOfWeek().equals(DayOfWeek.SATURDAY) && !now.getDayOfWeek().equals(DayOfWeek.SUNDAY) && isWorkHour;
        };


    }

    /**
     * 保存到mongo
     *
     * @param openKfId       打开kf id
     * @param externalUserId 外部用户id
     */
    private void saveToMongo(String openKfId, String externalUserId, String customUserName, ExportConcatEntity entity) {
        try {
            Assert.notNull(openKfId, () -> new RuntimeException("存储聊天记录时候 客服id不能为空！"));
            Assert.notNull(externalUserId, () -> new RuntimeException("存储聊天记录时候 客户id不能为空！"));
//            String collectionNameRegex = entity.getKfName() + ":" + openKfId + ":" + externalUserId;
//
//            // 查询第一层是否存在
//            Query query = Query.query(Criteria.where("_id").is(entity.getId()));
//
//            if (mongoTemplate.findOne(query, ConcatEntity.class,collectionNameRegex) == null) {
//                // 不存在的话，新增第一层，同时插入数据
//                mongoTemplate.insert(entity, collectionNameRegex);
//            } else {
//                Assert.notNull(entity.getCommunicationList(), () -> new RuntimeException("对话内容不能为空！"));
//
//                ConcatInnerEntity concatInnerEntity = entity.getCommunicationList().get(0);
//                // 更新第二层
//                Update update = new Update().push("communicationList", concatInnerEntity);
//                mongoTemplate.updateMulti(query, update, ConcatEntity.class, collectionNameRegex);
//            }
            // id规则发生改变， 客服名称+客服id+接待人工号+客户id
            String collectionNameRegex = entity.getKfName() + ":" + entity.getReceptionistNo() + ":" + customUserName +
                    ":" + externalUserId + ":" + openKfId;
            if (!mongoTemplate.collectionExists(collectionNameRegex)) {
                mongoTemplate.createCollection(collectionNameRegex);
            }
            mongoTemplate.insert(entity, collectionNameRegex);

        } catch (Exception e) {
            log.error("mongodb存储失败了，原因是：{}", e.getMessage(), e);
        }

    }


}
